/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../bif.h"

#include "../cm.h"
#include "../smc.h"
#include "../smc_tbls.h"

#include "../regs.h"

#include "dyn_pm.h"
#include "ctx.h"
#include "private.h"
#include "smc_state_tbl.h"
#include "smc_cac_cfg_tbl.h"
#include "smc_mc_reg_tbl.h"
#include "smc_mc_arb_tbl.h"
#include "smc_eng_pll_tbl.h"
#include "smc_dte_cfg_tbl.h"

static void tbls_cpy(struct ctx *ctx,	struct smc_state_tbl *smc_state_tbl,
					struct smc_cac_cfg_tbl *smc_cac_cfg_tbl,
					struct smc_mc_reg_tbl *smc_mc_reg_tbl,
					struct smc_mc_arb_tbl *smc_mc_arb_tbl,
					struct smc_eng_pll_tbl *smc_eng_pll_tbl,
					struct smc_dte_cfg_tbl *smc_dte_cfg_tbl)
{
	u32 smc_ram_tbl_of;

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
							+ SMC_FW_HDR_STATE_TBL);

	LOG("SMC_FW_HDR_STATE_TBL=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_state_tbl,
							sizeof(*smc_state_tbl));
#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	memset(smc_state_tbl, 0, sizeof(*smc_state_tbl));
	smc_memcpy_from(ctx->dev, (u8*)smc_state_tbl, smc_ram_tbl_of,
							sizeof(*smc_state_tbl));
	smc_state_tbl_dump(smc_state_tbl);
#endif

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
						+ SMC_FW_HDR_CAC_CFG_TBL);

	LOG("SMC_FW_HDR_CAC_CFG_TBL=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_cac_cfg_tbl,
						sizeof(*smc_cac_cfg_tbl));	

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	memset(smc_cac_cfg_tbl, 0, sizeof(*smc_cac_cfg_tbl));
	smc_memcpy_from(ctx->dev, (u8*)smc_cac_cfg_tbl, smc_ram_tbl_of,
						sizeof(*smc_cac_cfg_tbl));
	smc_cac_cfg_tbl_dump(smc_cac_cfg_tbl);
#endif

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
							+ SMC_FW_HDR_MC_TBL);

	LOG("SMC_FW_HDR_MC_TBL=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_mc_reg_tbl,
						sizeof(*smc_mc_reg_tbl));
#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	memset(smc_mc_reg_tbl, 0, sizeof(*smc_mc_reg_tbl));
	smc_memcpy_from(ctx->dev, (u8*)smc_mc_reg_tbl, smc_ram_tbl_of,
						sizeof(*smc_mc_reg_tbl));
	smc_mc_reg_tbl_dump(smc_mc_reg_tbl);
#endif

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
						+ SMC_FW_HDR_MC_ARB_TBL);

	LOG("SMC_FW_HDR_MC_ARB_TBL=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_mc_arb_tbl,
						sizeof(*smc_mc_arb_tbl));

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	memset(smc_mc_arb_tbl, 0, sizeof(*smc_mc_arb_tbl));
	smc_memcpy_from(ctx->dev, (u8*)smc_mc_arb_tbl, smc_ram_tbl_of,
						sizeof(*smc_mc_arb_tbl));
	smc_mc_arb_tbl_dump(smc_mc_arb_tbl);
#endif

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
						+ SMC_FW_HDR_ENG_PLL_TBL);

	LOG("SMC_FW_HDR_ENG_PLL_TBL=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_eng_pll_tbl,
						sizeof(*smc_eng_pll_tbl));

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	memset(smc_eng_pll_tbl, 0, sizeof(*smc_eng_pll_tbl));
	smc_memcpy_from(ctx->dev, (u8*)smc_eng_pll_tbl, smc_ram_tbl_of,
						sizeof(*smc_eng_pll_tbl));
	smc_eng_pll_tbl_dump(smc_eng_pll_tbl);
#endif

	/*--------------------------------------------------------------------*/

	smc_ram_tbl_of = smc_r32(ctx->dev, SMC_FW_HDR_START
						+ SMC_FW_HDR_DTE_CFG_TBL);

	LOG("SMC_FW_HDR_DTE_CFG_TBL=0x%08x", smc_ram_tbl_of);

	smc_memcpy_to(ctx->dev, smc_ram_tbl_of, (u8*)smc_dte_cfg_tbl, 
						sizeof(*smc_dte_cfg_tbl));
#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
	memset(smc_dte_cfg_tbl, 0, sizeof(*smc_dte_cfg_tbl));
	smc_memcpy_from(ctx->dev, (u8*)smc_dte_cfg_tbl, smc_ram_tbl_of,
						sizeof(*smc_dte_cfg_tbl));
	smc_dte_cfg_tbl_dump(smc_dte_cfg_tbl);
#endif

	/*--------------------------------------------------------------------*/
}

long smc_tbls_init(struct ctx *ctx)
{
	struct smc_state_tbl *smc_state_tbl;
	struct smc_cac_cfg_tbl *smc_cac_cfg_tbl;
	struct smc_mc_reg_tbl *smc_mc_reg_tbl;
	struct smc_mc_arb_tbl *smc_mc_arb_tbl;
	struct smc_eng_pll_tbl *smc_eng_pll_tbl;
	struct smc_dte_cfg_tbl *smc_dte_cfg_tbl;
	long r;

	/*--------------------------------------------------------------------*/

	smc_state_tbl = kzalloc(sizeof(*smc_state_tbl), GFP_KERNEL);
	if (!smc_state_tbl) {
		dev_err(&ctx->dev->dev, "dyn_pm:enable to allocate memory for the smc state table\n");
		goto err;
	}

	r = smc_state_tbl_init(ctx, smc_state_tbl);
	if (r == -SI_ERR)
		goto err_free_state_tbl;

	/*--------------------------------------------------------------------*/

	smc_mc_reg_tbl = kzalloc(sizeof(*smc_mc_reg_tbl), GFP_KERNEL);
	if (!smc_mc_reg_tbl) {
		dev_err(&ctx->dev->dev, "dyn_pm:enable to allocate memory for the smc memory controller register table\n");
		goto err_free_state_tbl;
	}

	smc_mc_reg_tbl_init(ctx, smc_state_tbl, smc_mc_reg_tbl);

	/*--------------------------------------------------------------------*/

	smc_mc_arb_tbl = kzalloc(sizeof(*smc_mc_arb_tbl), GFP_KERNEL);
	if (!smc_mc_arb_tbl) {
		dev_err(&ctx->dev->dev, "dyn_pm:enable to allocate memory for the smc memory controller arb table\n");
		goto err_free_mc_reg_tbl;
	}

	r = smc_mc_arb_tbl_init(ctx, smc_state_tbl, smc_mc_arb_tbl);
	if (r == -SI_ERR)
		goto err_free_mc_arb_tbl;

	/*--------------------------------------------------------------------*/

	smc_eng_pll_tbl = kzalloc(sizeof(*smc_eng_pll_tbl), GFP_KERNEL);
	if (!smc_eng_pll_tbl) {
		dev_err(&ctx->dev->dev, "dyn_pm:enable to allocate memory for the smc engine pll table\n");
		goto err_free_mc_arb_tbl;
	}

	r = smc_eng_pll_tbl_init(ctx, smc_eng_pll_tbl);
	if (r == -SI_ERR)
		goto err_free_eng_pll_tbl;

	/*--------------------------------------------------------------------*/

	smc_dte_cfg_tbl = kzalloc(sizeof(*smc_dte_cfg_tbl), GFP_KERNEL);
	if (!smc_dte_cfg_tbl) {
		dev_err(&ctx->dev->dev, "dyn_pm:enable to allocate memory for the smc digital temperature estimation configuration table\n");
		goto err_free_eng_pll_tbl;
	}

	smc_dte_cfg_tbl_init(ctx, smc_dte_cfg_tbl);

	/*--------------------------------------------------------------------*/

	smc_cac_cfg_tbl = kzalloc(sizeof(*smc_cac_cfg_tbl), GFP_KERNEL);
	if (!smc_cac_cfg_tbl) {
		dev_err(&ctx->dev->dev, "dyn_pm:enable to allocate memory for the smc cac configuration table\n");
		goto err_free_dte_cfg_tbl;
	}

	smc_cac_cfg_tbl_init(ctx, smc_cac_cfg_tbl);

	/*--------------------------------------------------------------------*/

	tbls_cpy(ctx, smc_state_tbl, smc_cac_cfg_tbl, smc_mc_reg_tbl,
			smc_mc_arb_tbl, smc_eng_pll_tbl, smc_dte_cfg_tbl);

	kfree(smc_cac_cfg_tbl);
	kfree(smc_dte_cfg_tbl);
	kfree(smc_eng_pll_tbl);
	kfree(smc_mc_arb_tbl);
	kfree(smc_mc_reg_tbl);
	kfree(smc_state_tbl);
	return 0;

err_free_dte_cfg_tbl:
	kfree(smc_dte_cfg_tbl);

err_free_eng_pll_tbl:
	kfree(smc_eng_pll_tbl);

err_free_mc_arb_tbl:
	kfree(smc_mc_arb_tbl);

err_free_mc_reg_tbl:
	kfree(smc_mc_reg_tbl);

err_free_state_tbl:
	kfree(smc_state_tbl);
err:
	return -SI_ERR;
}
